```html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>RabbitMQ 消息队列深度解析 - 死信队列与延时队列</title>
    <link href="https://cdn.staticfile.org/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
    <link href="https://cdn.staticfile.org/tailwindcss/2.2.19/tailwind.min.css" rel="stylesheet">
    <link href="https://fonts.googleapis.com/css2?family=Noto+Serif+SC:wght@400;500;600;700&family=Noto+Sans+SC:wght@300;400;500;700&display=swap" rel="stylesheet">
    <script src="https://cdn.jsdelivr.net/npm/mermaid@latest/dist/mermaid.min.js"></script>
    <style>
        body {
            font-family: 'Noto Sans SC', 'Noto Serif SC', Tahoma, Arial, Roboto, "Droid Sans", "Helvetica Neue", "Droid Sans Fallback", "Heiti SC", "Hiragino Sans GB", Simsun, sans-serif;
            background-color: #f8fafc;
            color: #1e293b;
            line-height: 1.6;
        }
        .hero {
            background: linear-gradient(135deg, #4f46e5 0%, #7c3aed 100%);
            color: white;
        }
        .code-block {
            background-color: #1e293b;
            color: #e2e8f0;
            border-radius: 0.5rem;
            padding: 1.5rem;
            font-family: 'Menlo', 'Monaco', 'Courier New', monospace;
            position: relative;
        }
        .code-block::before {
            content: attr(data-lang);
            position: absolute;
            top: 0;
            left: 0;
            padding: 0.25rem 1rem;
            background-color: #334155;
            color: #94a3b8;
            border-radius: 0.5rem 0 0.5rem 0;
            font-size: 0.75rem;
        }
        .highlight {
            background-color: #f1f5f9;
            border-left: 4px solid #4f46e5;
            padding: 1rem;
            margin: 1rem 0;
        }
        .visualization {
            background-color: white;
            border-radius: 0.5rem;
            padding: 1.5rem;
            box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
            margin: 2rem 0;
        }
        .info-card {
            background-color: white;
            border-radius: 0.5rem;
            padding: 1.5rem;
            box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
            transition: transform 0.2s, box-shadow 0.2s;
        }
        .info-card:hover {
            transform: translateY(-4px);
            box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
        }
        .section-title {
            position: relative;
            padding-left: 1.5rem;
        }
        .section-title:before {
            content: "";
            position: absolute;
            left: 0;
            top: 0;
            bottom: 0;
            width: 4px;
            background: linear-gradient(to bottom, #4f46e5, #7c3aed);
            border-radius: 2px;
        }
        .diagram {
            background-color: white;
            border-radius: 0.5rem;
            padding: 1.5rem;
            box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
            margin: 2rem 0;
            min-height: 300px;
        }
        .quote {
            border-left: 4px solid #4f46e5;
            padding-left: 1.5rem;
            color: #475569;
            font-style: italic;
        }
    </style>
</head>
<body class="antialiased">
    <!-- Hero Section -->
    <section class="hero py-20 px-4 md:px-0">
        <div class="container mx-auto max-w-5xl px-4">
            <div class="flex flex-col md:flex-row items-center">
                <div class="md:w-1/2 mb-10 md:mb-0">
                    <h1 class="text-4xl md:text-5xl font-bold leading-tight mb-4">RabbitMQ 消息队列深度解析</h1>
                    <h2 class="text-2xl md:text-3xl font-semibold text-indigo-200 mb-6">死信队列与延时队列实战指南</h2>
                    <p class="text-lg text-indigo-100 mb-8">探索消息队列中的高级特性，构建更健壮的分布式系统</p>
                    <div class="flex space-x-4">
                        <a href="#dead-letter" class="px-6 py-3 bg-white text-indigo-700 font-medium rounded-lg hover:bg-indigo-50 transition">死信队列</a>
                        <a href="#delay-queue" class="px-6 py-3 border-2 border-white text-white font-medium rounded-lg hover:bg-white hover:bg-opacity-10 transition">延时队列</a>
                    </div>
                </div>
                <div class="md:w-1/2 flex justify-center">
                    <div class="relative w-full max-w-md">
                        <div class="absolute w-full h-full bg-indigo-400 rounded-xl opacity-20 transform rotate-6"></div>
                        <div class="relative bg-white p-6 rounded-xl shadow-2xl">
                            <div class="flex mb-4">
                                <div class="w-3 h-3 rounded-full bg-red-500 mr-2"></div>
                                <div class="w-3 h-3 rounded-full bg-yellow-500 mr-2"></div>
                                <div class="w-3 h-3 rounded-full bg-green-500"></div>
                            </div>
                            <div class="code-block" data-lang="RabbitMQ">
                                <pre><code>+---------------+       +----------------+       +---------------+
|   Producer    | ----> |   RabbitMQ     | ----> |   Consumer    |
|  (Publisher)  |       |   (Broker)     |       |  (Subscriber) |
+---------------+       +----------------+       +---------------+</code></pre>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </section>

    <!-- Main Content -->
    <main class="container mx-auto max-w-5xl px-4 py-12">
        <!-- Dead Letter Queue Section -->
        <section id="dead-letter" class="mb-20">
            <div class="flex items-center mb-8">
                <i class="fas fa-exclamation-triangle text-3xl text-red-500 mr-4"></i>
                <h2 class="text-3xl font-bold">死信队列 (Dead Letter Queue)</h2>
            </div>

            <!-- What is DLQ -->
            <article class="mb-12">
                <h3 class="section-title text-2xl font-semibold mb-6">什么是死信队列</h3>
                <div class="grid grid-cols-1 md:grid-cols-2 gap-8">
                    <div>
                        <p class="mb-4">一般来说，Producer将消息投递到Queue中，Consumer从Queue取出消息进行消费，但某些时候由于特定的原因导致Queue中的某些消息无法被消费，这样的消息如果没有后续的处理，就变成了死信(Dead Letter)，所有的死信都会放到死信队列中，统一的处理。</p>
                        <p class="mb-4">"死信"消息会被RabbitMQ进行特殊处理，如果配置了死信队列信息，那么该消息将会被丢进死信队列中，如果没有配置，则该消息将会被丢弃。</p>
                        
                        <div class="highlight mb-6">
                            <h4 class="font-semibold text-lg mb-3">消费者消费消息的几种情况：</h4>
                            <ol class="list-decimal pl-5 space-y-2">
                                <li>正常消费 → 手动ack → MQ从队列中删除消息</li>
                                <li>消费者报错 → 没有ack → 消息是待应答状态 → channel断开后 → 消费恢复为待分配状态</li>
                                <li>消费者报错 → 手动nack → 
                                    <ul class="list-disc pl-5 mt-1">
                                        <li>如果配置了死信队列消息会被发送到死信队列中</li>
                                        <li>如果没有配置会被放入队列首部，如果消费者设置了requeue=false，则消息被丢弃</li>
                                    </ul>
                                </li>
                            </ol>
                        </div>
                    </div>
                    <div>
                        <img src="https://cdn.nlark.com/yuque/0/2022/png/21449790/1652524802472-1f89236d-722e-4670-960e-3c5cee8fbcf1.png" alt="死信队列流程图" class="w-full rounded-lg shadow-md">
                    </div>
                </div>
            </article>

            <!-- DLQ Sources -->
            <article class="mb-12">
                <h3 class="section-title text-2xl font-semibold mb-6">死信队列的来源</h3>
                <div class="grid grid-cols-1 md:grid-cols-3 gap-4">
                    <div class="info-card">
                        <div class="flex items-center mb-3">
                            <div class="w-10 h-10 rounded-full bg-red-100 flex items-center justify-center mr-3">
                                <i class="fas fa-ban text-red-500"></i>
                            </div>
                            <h4 class="font-semibold text-lg">消息被拒绝</h4>
                        </div>
                        <p>当消息被拒绝（basic.reject或basic.nack）并且requeue=false时，消息会成为死信</p>
                    </div>
                    <div class="info-card">
                        <div class="flex items-center mb-3">
                            <div class="w-10 h-10 rounded-full bg-yellow-100 flex items-center justify-center mr-3">
                                <i class="fas fa-clock text-yellow-500"></i>
                            </div>
                            <h4 class="font-semibold text-lg">消息TTL过期</h4>
                        </div>
                        <p>当消息存活时间(TTL)过期而未被消费时，消息会成为死信</p>
                    </div>
                    <div class="info-card">
                        <div class="flex items-center mb-3">
                            <div class="w-10 h-10 rounded-full bg-purple-100 flex items-center justify-center mr-3">
                                <i class="fas fa-arrows-alt-v text-purple-500"></i>
                            </div>
                            <h4 class="font-semibold text-lg">队列达到最大长度</h4>
                        </div>
                        <p>当队列达到最大长度（队列满了，无法再添加数据到MQ中）时，新消息会成为死信</p>
                    </div>
                </div>
            </article>

            <!-- Configuration -->
            <article class="mb-12">
                <h3 class="section-title text-2xl font-semibold mb-6">配置死信队列</h3>
                <div class="grid grid-cols-1 md:grid-cols-2 gap-8">
                    <div>
                        <h4 class="font-semibold text-lg mb-3">Java 配置示例</h4>
                        <div class="code-block" data-lang="Java">
<pre><code>@Configuration
public class RabbitMQConfig {

    // 声明业务Exchange
    @Bean
    public TopicExchange businessExchange(){
        return new TopicExchange("businessExchange");
    }

    // 声明业务队列A
    @Bean
    public Queue businessQueue(){
        Map&lt;String, Object&gt; args = new HashMap&lt;&gt;();
        // x-dead-letter-exchange 声明当前队列绑定的死信交换机
        args.put("x-dead-letter-exchange", "deadLetterExchange");
        // x-dead-letter-routing-key 声明当前队列的死信路由key
        args.put("x-dead-letter-routing-key", "dle.err");
        return new Queue("businessQueue",true,false,false,args);
    }

    // 声明业务队列A绑定关系
    @Bean
    public Binding businessBinding(Queue businessQueue, TopicExchange businessExchange){
        return BindingBuilder.bind(businessQueue).to(businessExchange).with("emp.*");
    }

    // 声明死信Exchange
    @Bean
    public TopicExchange deadLetterExchange(){
        return new TopicExchange("deadLetterExchange");
    }

    // 声明死信队列A
    @Bean
    public Queue deadLetterQueue(){
        return new Queue("dle-queue");
    }

    @Bean
    public Binding deadLetterQueueBinding(Queue deadLetterQueue, TopicExchange deadLetterExchange){
        return BindingBuilder.bind(deadLetterQueue).to(deadLetterExchange).with("dle.*");
    }
}</code></pre>
                        </div>
                    </div>
                    <div>
                        <h4 class="font-semibold text-lg mb-3">YAML 配置</h4>
                        <div class="code-block" data-lang="YAML">
<pre><code>spring:
  rabbitmq:
    host: 192.168.193.88
    port: 5672
    username: guest
    password: guest
    virtual-host: /
    listener:
      simple:
        acknowledge-mode: manual # 设置手动ack</code></pre>
                        </div>

                        <h4 class="font-semibold text-lg mt-6 mb-3">消费者实现</h4>
                        <div class="code-block" data-lang="Java">
<pre><code>@Component
public class DedaLetterListener {

    // 监听业务队列
    @RabbitListener(queues = "businessQueue")
    public void businessQueue(String msg, Channel channel, Message message) throws IOException {
        if ("error".equals(msg)) {
            System.out.println("业务消费者出现问题:" + msg);
            try {
                throw new RuntimeException();
            }catch (Exception e){
                // 无法消费消息，nack
                channel.basicNack(message.getMessageProperties().getDeliveryTag(),false,false);
            }
        } else {
            System.out.println("正常消费消息:" + msg);
            // 正常消费了消息，手动ack
            channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
        }
    }

    // 监听死信队列
    @RabbitListener(queues = "dle-queue")
    public void deadLetterQueue(String msg, Channel channel, Message message) throws IOException {
        System.out.println("死信队列消费消息:" + msg);
        channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
    }
}</code></pre>
                        </div>
                    </div>
                </div>
            </article>

            <!-- DLQ Changes -->
            <article class="mb-12">
                <h3 class="section-title text-2xl font-semibold mb-6">死信消息的变化</h3>
                <div class="highlight">
                    <p>如果队列配置了参数 x-dead-letter-routing-key 的话，"死信"的路由key将会被替换成该参数对应的值。如果没有设置，则保留该消息原有的路由key。</p>
                    <p class="mt-3">比如：</p>
                    <p>如果原有消息的路由key是testA，被发送到业务Exchage中，然后被投递到业务队列QueueA中，如果该队列没有配置参数x-dead-letter-routing-key，则该消息成为死信后，将保留原有的路由keytestA，如果配置了该参数，并且值设置为testB，那么该消息成为死信后，路由key将会被替换为testB，然后被抛到死信交换机中。</p>
                </div>
            </article>

            <!-- DLQ Use Cases -->
            <article class="mb-12">
                <h3 class="section-title text-2xl font-semibold mb-6">死信队列的应用场景</h3>
                <div class="quote">
                    <p>一般用在较为重要的业务队列中，确保未被正确消费的消息不被丢弃，一般发生消费异常可能原因主要有由于消息信息本身存在错误导致处理异常，处理过程中参数校验异常，或者因网络波动导致的查询异常等等，当发生异常时，当然不能每次通过日志来获取原消息，然后让运维帮忙重新投递消息。通过配置死信队列，可以让未正确处理的消息暂存到另一个队列中，待后续排查清楚问题后，编写相应的处理代码来处理死信消息，这样比手工恢复数据要好太多了。</p>
                </div>
            </article>
        </section>

        <!-- Delay Queue Section -->
        <section id="delay-queue" class="mb-20">
            <div class="flex items-center mb-8">
                <i class="fas fa-clock text-3xl text-yellow-500 mr-4"></i>
                <h2 class="text-3xl font-bold">延时队列 (Delay Queue)</h2>
            </div>

            <!-- What is Delay Queue -->
            <article class="mb-12">
                <h3 class="section-title text-2xl font-semibold mb-6">什么是延时队列</h3>
                <div class="grid grid-cols-1 md:grid-cols-2 gap-8">
                    <div>
                        <p class="mb-4">延迟队列存储的对象是对应的延时消息，所谓"延时消息"是指当消息被发送以后，并不想让消费者立即拿到消息，而是等待指定时间后，消费者才拿到这个消息进行消费。</p>
                        <p>普通队列中的元素总是等着希望被早点取出处理，而延时队列中的元素则是希望被在指定时间得到取出和处理。</p>
                    </div>
                    <div>
                        <img src="https://cdn.nlark.com/yuque/0/2022/png/21449790/1652524845705-66aacee3-9023-4dfe-bb77-ec842ec5d9df.png" alt="延时队列流程图" class="w-full rounded-lg shadow-md">
                    </div>
                </div>
            </article>

            <!-- Delay Queue Setup -->
            <article class="mb-12">
                <h3 class="section-title text-2xl font-semibold mb-6">延时队列的设置</h3>
                <div class="mb-6">
                    <p class="mb-4">RbbitMQ中存在TTL机制，<code>一条消息或者该队列中的所有消息的最大存活时间</code>，单位是毫秒。换句话说，如果一条消息设置了TTL属性或者进入了设置TTL属性的队列，那么这条消息如果在TTL设置的时间内没有被消费，则会成为"死信"。如果同时配置了队列的TTL和消息的TTL，那么较小的那个值将会被使用。</p>
                    
                    <div class="grid grid-cols-1 md:grid-cols-2 gap-6 mt-6">
                        <div>
                            <h4 class="font-semibold text-lg mb-3">给消息设置TTL时间</h4>
                            <div class="code-block" data-lang="Java">
<pre><code>Map&lt;String, Object&gt; args = new HashMap&lt;String, Object&gt;();
args.put("x-message-ttl", 6000); // 单位毫秒
channel.queueDeclare(queueName, durable, exclusive, autoDelete, args);</code></pre>
                            </div>
                            <p class="mt-3">每条消息的超时时间是6s，如果6s内没有被消费者消费，该消息就会变成死信。</p>
                        </div>
                        <div>
                            <h4 class="font-semibold text-lg mb-3">给队列设置超时时间</h4>
                            <div class="code-block" data-lang="Java">
<pre><code>@Bean
public Queue businessQueue1(){
    Map&lt;String, Object&gt; args = new HashMap&lt;&gt;();
    args.put("x-message-ttl", 5000);  // 这个队列中的所有的消息最多能活5s
    return new Queue("5-queue",true,false,false,args);
}</code></pre>
                            </div>
                            <p class="mt-3">但这两种方式是有区别的，<strong>如果设置了队列的TTL属性，那么一旦消息过期，就会被队列丢弃，而第二种方式，消息即使过期，也不一定会被马上丢弃，因为消息是否过期是在即将投递到消费者之前判定的，如果当前队列有严重的消息积压情况，则已过期的消息也许还能存活较长时间</strong></p>
                        </div>
                    </div>
                </div>
                <div class="highlight">
                    <p>为什么这两种方法处理的方式不一样？因为第二种方法里，队列中己过期的消息肯定在队列头部， RabbitMQ 只要定期从队头开始扫描是否有过期的消息即可。而第一种方法里，每条消息的过期时间不同，如果要删除所有过期消息势必要扫描整个队列，所以不如等到此消息即将被消费时再判定是否过期，如果过期再进行删除即可。</p>
                    <p class="mt-3">RabbitMQ 会确保在过期时间到达后将队列删除，但是不保障删除的动作有多及时 。在RabbitMQ 重启后，持久化的队列的过期时间会被重新计算。</p>
                </div>
            </article>

            <!-- Delay Queue Configuration -->
            <article class="mb-12">
                <h3 class="section-title text-2xl font-semibold mb-6">配置延时队列</h3>
                <div class="grid grid-cols-1 md:grid-cols-2 gap-8">
                    <div>
                        <div class="code-block" data-lang="Java">
<pre><code>@Configuration
public class RabbitMQConfigTTL {

    // 声明业务Exchange
    @Bean
    public TopicExchange businessExchange(){
        return new TopicExchange("ttl-Exchange");
    }

    // 创建延时队列1
    @Bean
    public Queue businessQueue1(){
        Map&lt;String, Object&gt; args = new HashMap&lt;&gt;();
        args.put("x-dead-letter-exchange", "deadLetterExchange");
        args.put("x-dead-letter-routing-key", "dle.err");
        args.put("x-message-ttl", 5000);   // 超时时间是5s
        return new Queue("5-queue",true,false,false,args);
    }

    // 创建延时队列2
    @Bean
    public Queue businessQueue2(){
        Map&lt;String, Object&gt; args = new HashMap&lt;&gt;();
        args.put("x-dead-letter-exchange", "deadLetterExchange");
        args.put("x-dead-letter-routing-key", "dle.err");
        args.put("x-message-ttl", 20000); // 超时时间是20s
        return new Queue("20-queue",true,false,false,args);
    }

    // 延时队列绑定关系
    @Bean
    public Binding businessBinding1(Queue businessQueue1, TopicExchange businessExchange){
        return BindingBuilder.bind(businessQueue1).to(businessExchange).with("emp.*");
    }

    // 延时队列绑定
    @Bean
    public Binding businessBinding2(Queue businessQueue2, TopicExchange businessExchange){
        return BindingBuilder.bind(businessQueue2).to(businessExchange).with("user.*");
    }

    //声明死信Exchange
    @Bean
    public TopicExchange deadLetterExchange(){
        return new TopicExchange("deadLetterExchange");
    }

    // 声明死信队列
    @Bean
    public Queue deadLetterQueue(){
        return new Queue("dle-queue",true,false,false,null);
    }

    // 死信队列绑定交换机
    @Bean
    public Binding deadLetterQueueBinding(Queue deadLetterQueue, TopicExchange deadLetterExchange){
        return BindingBuilder.bind(deadLetterQueue).to(deadLetterExchange).with("dle.*");
    }
}</code></pre>
                        </div>
                    </div>
                    <div>
                        <h4 class="font-semibold text-lg mb-3">YAML 配置</h4>
                        <div class="code-block" data-lang="YAML">
<pre><code>spring:
  rabbitmq:
    host: 192.168.193.88
    port: 5672
    username: guest
    password: guest
    virtual-host: /
    listener:
      simple:
        acknowledge-mode: manual # 设置手动ack</code></pre>
                        </div>

                        <h4 class="font-semibold text-lg mt-6 mb-3">提供者实现</h4>
                        <div class="code-block" data-lang="Java">
<pre><code>@Autowired
RabbitTemplate rabbitTemplate;

@RequestMapping("/send")
public void send(String msg){
    System.out.println("msg = [" + msg + "]");
    rabbitTemplate.convertAndSend("businessExchange","emp.add",msg);
}</code></pre>
                        </div>

                        <h4 class="font-semibold text-lg mt-6 mb-3">消费者实现</h4>
                        <div class="code-block" data-lang="Java">
<pre><code>@RabbitListener(queues = "dle-queue")
public void dleQueue(String msg, Channel channel, Message message) throws IOException {
    System.out.println("dleQueue1:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
    channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
}</code></pre>
                        </div>
                    </div>
                </div>
            </article>
        </section>

        <!-- Message Acknowledgement -->
        <section class="mb-20">
            <div class="flex items-center mb-8">
                <i class="fas fa-check-circle text-3xl text-green-500 mr-4"></i>
                <h2 class="text-3xl font-bold">消费者消息确认机制</h2>
            </div>

            <article class="mb-12">
                <div class="highlight">
                    <p>为了保证消息从队列可达到消费者， RabbitMQ 提供了消息确认机制 message acknowledgement。消费者在订阅队列时，可以指定 autoAck 参数，当 autoAck 等于 false 时， RabbitMQ 会等待消费者显式地回复确认信号后才从内存（或者磁盘）中移去消息（实质上是先打上删除标记，之后再删除）。</p>
                    <p class="mt-3">当 autoAck 等于 true 时， RabbitMQ 会自动把发送出去的消息置为确认，然后从内存（或者磁盘）中删除，而不管消费者是否真正地消费到了这些消息采用消息确认机制后，只要设置 autoAck 参数为 false，消费者就有足够的时间处理消息（任务），不用担心处理消息过程中消费者进程挂掉后消息丢失的问题，因为 RabbitMQ 会一直等待持有消息直到消费者显式调 Basic.Ack 命令为止。</p>
                    <p class="mt-3">当 autoAck 参数置为 false，对于 RabbitMQ 服务端而言，队列中的消息分成了两个部分：一部分是等待投递给消费者的消息；一部分是己经投递给消费者，但是还没有收到消费者确认信号的消息。如果 RabbitMQ 一直没有收到消费者的确认信号，并且消费此消息的消费者己经断开连接，则 RabbitMQ 会安排该消息重新进入队列，等待投递给下一个消费者，当然也有可能还是原来的那个消费者。</p>
                    <p class="mt-3">RabbitMQ 不会为未确认的消息设置过期时间，它判断此消息是否需要重新投递给消费者的唯一依据是消费该消息的消费者连接是否己经断开，这么设计的原因是 RabbitMQ 允许消费者消费一条消息的时间可以很久很久。</p>
                </div>
            </article>

            <div class="grid grid-cols-1 md:grid-cols-2 gap-8">
                <div>
                    <h3 class="section-title text-2xl font-semibold mb-6">自动ACK</h3>
                    <div class="info-card">
                        <p><code>ack</code>分为自动ack和手动ack两种。如果是自动ack，有两个弊端:</p>
                        <ol class="list-decimal pl-5 mt-3 space-y-2">
                            <li>MQ 只需要确认消息发送成功，无需等待应答就会丢弃消息，这样导致如果消费者客户端还未处理完消息，出现异常或者断电时消息丢失的后果。</li>
                            <li>自动ack没有<code>qos控制</code>，可能消费者客户端因为瞬间收到太多消息导致服务挂掉</li>
                        </ol>
                        <p class="mt-3">所以，常用的是<code>手动ack</code>应答</p>
                    </div>
                </div>
                <div>
                    <h3 class="section-title text-2xl font-semibold mb-6">手动ACK</h3>
                    <div class="info-card">
                        <p>手动ack存在弊端:</p>
                        <p class="mt-2">如果消费者存在Bug的话，就会导致所有的消息都抛出异常，然后队列的<code>Unacked消息数</code>暴涨，导致MQ响应越来越慢，然后down掉。</p>
                        <p class="mt-3">原因:因为上面消费者抛出异常，所以MQ没有得到ack响应，<strong>注意：这些消息会堆积在Unacked消息里，不会抛弃，即使另外打开一个消费者也不会被消费，直到原来的消费者客户端断开重连时，才会变成ready，这时如果通过<code>qos</code>设置了<code>prefetch</code>，没有ack响应的话，Broker不会再分配新的消息下来，就导致了阻塞</strong></p>
                    </div>
                </div>
            </div>

            <div class="mt-8">
                <h3 class="section-title text-2xl font-semibold mb-6">NACK</h3>
                <div class="info-card">
                    <p><code>nack</code>是什么呢？其实就是会通知MQ把消息塞回的队列头部（不是尾部），而不是变成Unacked，这样消费者客户端可以直接获取到这条消息。但是问题又来了，如果消费者有问题，那就算放回队列头部了，下次取出消费，还是会报错，又被送回队列头部，这样就<strong>陷入死循环</strong>了。</p>
                </div>
            </div>
        </section>

        <!-- Message Rejection -->
        <section class="mb-20">
            <h2 class="text-3xl font-bold mb-8">消费者消息拒绝</h2>
            
            <article class="mb-12">
                <div class="highlight">
                    <p>在消费者接收到消息后，如果想明确拒绝当前的消息而不是确认，那么应该怎么做呢？可以使用Basic.Reject 这个命令，消费者客户端可以调用与其对应的 channel.basicReject 方法来告诉 RabbitMQ 拒绝这个消息。Channel 类中的 basicReject 方法定义如下：</p>
                    <div class="code-block mt-4" data-lang="Java">
<pre><code>void basicReject(long deliveryTag, boolean requeue) throws IOException;</code></pre>
                    </div>
                    <p class="mt-3"><strong>deliveryTag</strong>：消息的编号。</p>
                    <p><strong>requeue</strong>：是否把拒绝后的消息重新入队列</p>
                    <p class="mt-3">如果参数设置为 true，则 RabbitMQ 会重新将这条消息存入队列，以便可以发送给下一个订阅的消费者；如果 requeue 参数设置为 false，则 RabbitMQ 立即会把消息从队列中移除，而不会把它发送给新的消费者。</p>
                    <p class="mt-3"><strong>也可以使用basicNack方法来拒绝</strong></p>
                    <p class="mt-3"><strong>注意点</strong></p>
                    <p>channel.basicReject 或者 channel.basicNack 中的 requeue 设直为 false，可以启用"死信队列"的功能。死信队列可以通过检测被拒绝或者未送达的消息来追踪问题。</p>
                </div>
            </article>

            <h3 class="section-title text-2xl font-semibold mb-6">消费者消费模式</h3>
            <div class="highlight">
                <p>RabbitMQ的消费模式分为两种：推（Push）模式和拉（Pull）模式，推模式采用 Basic Consume 进行消费，而拉模式则是调用 Basic Get 进行消费。</p>
                <div class="code-block mt-4" data-lang="Java">
<pre><code>public String basicConsume(String queue, boolean autoAck, Consumer callback) // 推模式
public GetResponse basicGet(String queue, boolean autoAck)  // 拉模式</code></pre>
                </div>
                <p class="mt-3">Basic Consume 将信道（Channel）直为接收模式，直到取消队列的订阅为止。在接收模式期间， RabbitMQ 会不断地推送消息给消费者，当然推送消息的个数还是会受到 Basic.Qos 的限制。</p>
                <p class="mt-3">如果只想从队列获得单条消息而不是持续订阅，建议还是使用 Basic.Get 进行消费。但是不能将 Basic.Get 放在一个循环里来代替 Basic.Consume，这样做会严重影响 RabbitMQ 的性能。如果要实现高吞吐量，消费者理应使用 Basic.Consume 方法。</p>
            </div>
        </section>

        <!-- Message Reliability -->
        <section class="mb-20">
            <h2 class="text-3xl font-bold mb-8">消息的可靠传输</h2>
            
            <div class="grid grid-cols-1 md:grid-cols-3 gap-6">
                <div class="info-card">
                    <h3 class="font-semibold text-xl mb-3">提供者</h3>
                    <ol class="list-decimal pl-5 space-y-1">
                        <li>事务 -- 确认消息已经到了队列</li>
                        <li>Confirm -- 确认消息已经到了交换机</li>
                        <li>return -- 消息没有路由到队列（在某些情况下，如果我们发送消息的时候，当前的exchange或者routeKey路由不到的时候，这个时候如果我们需要监听这种不可到达的消息，就要使用Return Listener）</li>
                    </ol>
                </div>
                <div class="info-card">
                    <h3 class="font-semibold text-xl mb-3">MQServer</h3>
                    <ol class="list-decimal pl-5 space-y-1">
                        <li>交换机，队列，消息全部持久化</li>
                        <li>镜像队列机制(解决MQ把消息持久化到磁盘时MQ宕机)</li>
                    </ol>
                </div>
                <div class="info-card">
                    <h3 class="font-semibold text-xl mb-3">消费者</h3>
                    <ol class="list-decimal pl-5 space-y-1">
                        <li>自动ack</li>
                        <li>手动ack</li>
                    </ol>
                </div>
            </div>
        </section>

        <!-- Message Duplication -->
        <section class="mb-20">
            <h2 class="text-3xl font-bold mb-8">消息的重复消费</h2>
            
            <article class="mb-8">
                <p>消息的重复消费场景如下：</p>
                <p class="mt-2">消息已经投递了消费者，消费者正常消费后准备给MQ应答ACK，此时网络出现了闪断。channel断开连接，消息再次被放入到MQ的头部，为了保证消息至少被消费一次，当网络恢复正常后MQ再次发送消息给消费者。此时同一个消息就会被消费两次。</p>
                <p class="mt-3">消息的幂等性: <code>使用同样的条件，一次请求和重复的多次请求对系统资源的影响是一致的</code>。</p>
            </article>

            <div class="grid grid-cols-1 md:grid-cols-2 gap-8">
                <div>
                    <h3 class="section-title text-2xl font-semibold mb-6">大量消息堆积如何处理</h3>
                    <div class="mb-6">
                        <h4 class="font-semibold text-lg mb-2">出现的原因</h4>
                        <ol class="list-decimal pl-5 space-y-1">
                            <li>消费者网络故障</li>
                            <li>消费者出现异常，没有ack</li>
                        </ol>
                    </div>
                    <img src="https://cdn.nlark.com/yuque/0/2022/png/21449790/1652524897463-72bc9190-241c-46dd-85c7-fd014c562d4d.png" alt="消息堆积原因" class="w-full rounded-lg shadow-md">
                </div>
                <div>
                    <h4 class="font-semibold text-lg mb-2">解决方案</h4>
                    <img src="https://cdn.nlark.com/yuque/0/2022/png/21449790/1652524921781-5187ebb0-c94d-4fc9-9759-4c4285988c9a.png" alt="消息堆积解决方案" class="w-full rounded-lg shadow-md">
                </div>
            </div>
        </section>

        <!-- MQ Pros and Cons -->
        <section class="mb-20">
            <h2 class="text-3xl font-bold mb-8">MQ优点和缺点</h2>
            
            <div class="grid grid-cols-1 md:grid-cols-2 gap-8">
                <div>
                    <h3 class="section-title text-2xl font-semibold mb-6">优点</h3>
                    <div class="info-card bg-green-50">
                        <ol class="list-decimal pl-5 space-y-2">
                            <li><strong>异步</strong> - 发送者和接收者不必同时在线，提高系统响应速度</li>
                            <li><strong>解耦</strong> - 生产者和消费者不需要知道对方的存在，只需关注队列</li>
                            <li><strong>削锋填谷</strong> - 在流量高峰期缓冲请求，防止系统过载</li>
                        </ol>
                    </div>
                </div>
                <div>
                    <h3 class="section-title text-2xl font-semibold mb-6">缺点</h3>
                    <div class="info-card bg-red-50">
                        <ol class="list-decimal pl-5 space-y-2">
                            <li><strong>系统的可用性降低</strong> - 如果MQ挂了，整个系统可能无法正常工作</li>
                            <li><strong>复杂度提高</strong> - 引入MQ增加了系统的复杂度和维护成本</li>
                            <li><strong>一致性问题</strong> - 在分布式系统中，保证数据一致性变得更加复杂</li>
                        </ol>
                    </div>
                </div>
            </div>
        </section>

        <!-- Message Ordering -->
        <section class="mb-20">
            <h2 class="text-3xl font-bold mb-8">MQ是如何解决消息有序性的</h2>
            
            <div class="code-block" data-lang="Plain Text">
<pre><code>1、在发送消息的时候就要确定消息消费的顺序
2、在消费者中消费消息的时候去判一下，这个消息的上一个是否被消费了
    a)如果上一个被消费，该消息可以消费，消费完后需要redis中记录一下这个消息。
    b)如果上一个没有被消费，把该消息nack。</code></pre>
            </div>
        </section>

        <!-- Rate Limiting -->
        <section class="mb-20">
            <h2 class="text-3xl font-bold mb-8">MQ如何实现限流操作</h2>
            
            <div class="highlight">
                <p>高并发，高扩展，高性能，高安全</p>
                <p class="mt-2">MQ可以通过设置队列的长度来达到限流</p>
            </div>
        </section>

        <!-- Message Duplication -->
        <section>
            <h2 class="text-3xl font-bold mb-8">MQ消息的重复消费</h2>
            
            <div class="mb-6">
                <h3 class="section-title text-2xl font-semibold mb-6">什么情况下会出现消息的重复消费</h3>
                <img src="https://cdn.nlark.com/yuque/0/2022/png/21449790/1652524958851-9739f631-54e9-45c7-8c60-808a07c0363e.png" alt="消息重复消费场景" class="w-full rounded-lg shadow-md mb-4">
                <ul class="list-disc pl-5 space-y-1">
                    <li><strong>待分配消息数量</strong>：MQServer还没有推送给消费者的消息。</li>
                    <li><strong>待应答消息数量</strong>：MQServer等待消费者应答的消息的数量(消费者正在消费)。</li>
                    <li><strong>total=上面数量相加</strong>。</li>
                </ul>
                <p class="mt-4">思考，如果一个消息的状态一直unacked，此时消费者断开连接，该消息状态变成如何？</p>
                <p class="mt-2">消费者断开，连接也就断开了，channl断开了，所有的待应答的消息都会变为待分配。</p>
                <p class="mt-2">原因在于消费者已经把<strong>数据插入到MySQL了</strong>，此时消费者还没来得及给MQ应答就宕机了。channel断开后所有的待应答的消息会全部的转为带分配状态。又把这个消息转给了其他的消费者<strong>再次的插入了MySQL</strong></p>
            </div>

            <div class="grid grid-cols-1 md:grid-cols-2 gap-8">
                <div>
                    <h3 class="section-title text-2xl font-semibold mb-6">出现消息的重复消费后会如何？</h3>
                    <div class="info-card">
                        <p>比如要做数据同步，期望数据同步MySQL只有1条，如果消息重复消费了就在MySQL中插入多条数据。</p>
                    </div>
                </div>
                <div>
                    <h3 class="section-title text-2xl font-semibold mb-6">如何解决重复消费的问题</h3>
                    <div class="info-card">
                        <p>在插入数据库的时候先判断一下，这条数据是否已经存在了，如果不存在就插入，如果存在就不插入。</p>
                        <p class="mt-2">发送消息的时候给每个消息分配一个唯一标识，消费者拿到消息后，先把这个去redis中看下该消息是否已经消费了，如果没有消费就正常消费，如果已经消费就ack。</p>
                    </div>
                </div>
            </div>
        </section>
    </main>

    <script>
        mermaid.initialize({
            startOnLoad: true,
            theme: 'default',
            flowchart: {
                useMaxWidth: true,
                htmlLabels: true,
                curve: 'basis'
            }
        });
    </script>
</body>
</html>
```